package multimedia.model.encoding;

import java.util.Vector;

import multimedia.model.ModifiedImage;
import multimedia.model.PixelCoordinate;
import multimedia.model.colorspace.ColorspaceInterface;
import multimedia.model.colorspace.RGB;

/**
 * @author trigueros
 * 
 */
public class Predictor
{
    /** Predictors */
    public static final int P1 = 1, P2 = 2, P3 = 3, P4 = 4, P5 = 5, P6 = 6, P7 = 7, P8 = 8;

    private int predictor;
    private ModifiedImage image;
    private PixelCoordinate pixMap;
    private int encodingFactor = 1;

    public Predictor(ModifiedImage image, int predictor)
    {
        this.image = image;
        pixMap = image.getImagePixelHolder();

        // Make sure that the predictor is within a certain range
        if ( predictor < 0 || predictor > 8 )
        {
            throw new NumberFormatException("Predictor number invalid");
        } else
            this.predictor = predictor;
    }

    /**
     * Will encode/decode the given {@link ModifiedImage} using the predictor denoted by the constructor.
     * @param isEncode if true it'll encode otherwise it'll decode
     */
    public void encodePixels( boolean isEncode )
    {
        // quit if predictor is 0
        if( predictor == 0 )
            return; 
        
        encodingFactor = ( isEncode ) ? 1 : -1;
        Vector<ColorspaceInterface> encodedVector = new Vector<ColorspaceInterface>();
        PixelCoordinate newPixMap = new PixelCoordinate( encodedVector, image.getImageSize().width );

        // Traverse entire matrix
        for ( int y = 0; y < image.getImageSize().height; y++ )
            for ( int x = 0; x < image.getImageSize().width; x++ )
            {
                /*
                 * There's four cases 1. The pixel is in 0,0 then we don't do anything. 2. The pixel
                 * is in the first row, for which we use predictive coding P1 3. The pixel is in the
                 * first column, for which we use predictive coding P2 4. The pixel is anywhere
                 * else, we use predictive coding denoted by predictor
                 */
                
                PixelCoordinate currentPixMap = null;
                // First time around this must be true
                if (  x == 0 && y == 0  )
                {
                    currentPixMap = pixMap;
                }
                else
                {
                    currentPixMap = ( isEncode ) ? pixMap : newPixMap;
                }
                
                // Set current pixel depending on 
                RGB currentPixel = new RGB(pixMap.getPixelAt(x, y));
                
                // Case 1
                if ( x == 0 && y == 0 )
                {
                    // do nothing put pixel in encodedVector
                    encodedVector.add(currentPixMap.getPixelAt(x, y));
                    continue;
                }
                // Case 2
                else if ( y == 0 )
                {
                    // P1
                    encodedVector.add(P1(currentPixel, new RGB(currentPixMap.getPixelAt(x - 1, y) )));
                    continue;

                } else if ( x == 0 )
                {
                    // P2
                    encodedVector.add(P2(currentPixel, new RGB(currentPixMap.getPixelAt(x, y - 1) )));

                } else
                {
                    // If we made it past this point then we know that we can pick A, B, and C
                    RGB A = new RGB(currentPixMap.getPixelAt(x - 1, y));
                    RGB B = new RGB(currentPixMap.getPixelAt(x, y - 1));
                    RGB C = new RGB(currentPixMap.getPixelAt(x - 1, y - 1));
                   
                    
                    // Px
                    switch ( predictor )
                    {
                        case P1:
                            encodedVector.add(P1(currentPixel, A));;
                            break;

                        case P2:
                            encodedVector.add(P2(currentPixel, B));;
                            break;

                        case P3:
                            encodedVector.add(P3(currentPixel, C));;
                            break;

                        case P4:
                            encodedVector.add(P4(currentPixel, A, B, C));
                            break;

                        case P5:
                            encodedVector.add(P5(currentPixel, A, B, C));
                            break;

                        case P6:
                            encodedVector.add(P6(currentPixel, A, B, C));
                            break;

                        case P7:
                            encodedVector.add(P7(currentPixel, A, B));
                            break;

                        case P8:
                            encodedVector.add(P8(currentPixel, A, B, C));
                            break;
                    }
                }
            }

        // Set the new pixels in the image
        image.setImagePixelHolder(new PixelCoordinate(encodedVector, image.getImageSize().width));

    }

    /**
     * Perform P1 encoding on one pixel. A
     * 
     * @param currentPixel
     * @param x
     * @param y
     * @param
     */
    private ColorspaceInterface P1( RGB currentPixel, RGB A )
    {
        // Prepare the references to the new pixel.
        RGB encodedPixel = new RGB();

        // Calculate the encoded values
        RGB intermediate = A.multBy( encodingFactor );
        encodedPixel = currentPixel.subtract(intermediate);

        return encodedPixel;

    }

    /**
     * Perform P2 encoding on one pixel. B
     * 
     * @param currentPixel
     * @param x
     * @param y
     * @return
     */
    private ColorspaceInterface P2( RGB currentPixel, RGB B )
    {
        // Prepare the references to the new pixel.
        RGB encodedPixel = new RGB();

        // Calculate the encoded values
        RGB intermediate = B.multBy( encodingFactor );
        encodedPixel = currentPixel.subtract(intermediate);

        return encodedPixel;
    }

    /**
     * Perform P3 encoding on one pixel. C
     * 
     * @param currentPixel
     * @param x
     * @param y
     * @return
     */
    private ColorspaceInterface P3( RGB currentPixel, RGB C )
    {
        // Prepare the references to the new pixel.
        RGB encodedPixel = new RGB();
        
        // Calculate the encoded values
        RGB intermediate = C.multBy( encodingFactor );
        encodedPixel = currentPixel.subtract(intermediate);

        return encodedPixel;
    }

    /**
     * Perform P4 encoding on one pixel. A + B - C
     * 
     * @param currentPixel
     * @param x
     * @param y
     * @return
     */
    private ColorspaceInterface P4( RGB currentPixel, RGB A, RGB B, RGB C )
    {
        // Prepare the references to the new pixel.
        RGB encodedPixel = new RGB();

        RGB intermediate = new RGB();

        intermediate = A.add(B);
        intermediate = intermediate.subtract(C);
        intermediate = intermediate.multBy( encodingFactor );
        
        encodedPixel = currentPixel.subtract(intermediate);

        return encodedPixel;
    }

    /**
     * Perform P5 encoding on one pixel. A + (B - C)/2
     * 
     * @param currentPixel
     * @param x
     * @param y
     * @return
     */
    private ColorspaceInterface P5( RGB currentPixel, RGB A, RGB B, RGB C )
    {
        // Prepare the references to the new pixel.
        RGB encodedPixel = new RGB();

        RGB intermediate = new RGB();

        intermediate = B.subtract(C);
        intermediate = intermediate.divideBy(2.0f);
        intermediate = A.add(intermediate);
        intermediate = intermediate.multBy( encodingFactor );

        encodedPixel = currentPixel.subtract(intermediate);

        return encodedPixel;
    }

    /**
     * Perform P6 encoding on one pixel. B + (A - C)/2
     * 
     * @param currentPixel
     * @param x
     * @param y
     * @return
     */
    private ColorspaceInterface P6( RGB currentPixel, RGB A, RGB B, RGB C )
    {
        // Prepare the references to the new pixel.
        RGB encodedPixel = new RGB();
        RGB intermediate = new RGB();

        intermediate = A.subtract(C);
        intermediate = intermediate.divideBy(2.0f);
        intermediate = B.add(intermediate);
        intermediate = intermediate.multBy( encodingFactor );

        encodedPixel = currentPixel.subtract(intermediate);

        return encodedPixel;
    }

    /**
     * Perform P7 encoding on one pixel. (A + B)/2
     * 
     * @param currentPixel
     * @param x
     * @param y
     * @return
     */
    private ColorspaceInterface P7( RGB currentPixel, RGB A, RGB B )
    {
        // Prepare the references to the new pixel.
        RGB encodedPixel = new RGB();

        RGB intermediate = new RGB();

        intermediate = A.add(B);
        intermediate = intermediate.divideBy(2.0f);
        intermediate = intermediate.multBy( encodingFactor );

        encodedPixel = currentPixel.subtract(intermediate);

        return encodedPixel;
    }

    /**
     * Perform P8 encoding on one pixel. A + B + C
     * 
     * @param currentPixel
     * @param x
     * @param y
     * @return
     */
    private ColorspaceInterface P8( RGB currentPixel, RGB A, RGB B, RGB C )
    {
        // Prepare the references to the new pixel.
        RGB encodedPixel = new RGB();

        RGB intermediate = new RGB();

        intermediate = A.add(B);
        intermediate = intermediate.add(C);
        intermediate = intermediate.multBy( encodingFactor );

        encodedPixel = currentPixel.subtract(intermediate);

        return encodedPixel;
    }
}
